msg_tool\scripts\entis_gls/srcxml.rs
1//! Entis GLS engine XML Script (.srcxml)
2use crate::ext::io::*;
3use crate::ext::rcdom::*;
4use crate::scripts::base::*;
5use crate::types::*;
6use crate::utils::encoding::*;
7use anyhow::Result;
8use markup5ever_rcdom::{Handle, RcDom, SerializableHandle};
9use xml5ever::driver::parse_document;
10use xml5ever::serialize::serialize;
11use xml5ever::tendril::TendrilSink;
12
13#[derive(Debug)]
14/// A builder for Entis GLS srcxml scripts.
15pub struct SrcXmlScriptBuilder {}
16
17impl SrcXmlScriptBuilder {
18 /// Creates a new instance of `SrcXmlScriptBuilder`.
19 pub fn new() -> Self {
20 Self {}
21 }
22}
23
24impl ScriptBuilder for SrcXmlScriptBuilder {
25 fn default_encoding(&self) -> Encoding {
26 Encoding::Utf8
27 }
28
29 fn build_script(
30 &self,
31 buf: Vec<u8>,
32 _filename: &str,
33 encoding: Encoding,
34 _archive_encoding: Encoding,
35 config: &ExtraConfig,
36 _archive: Option<&Box<dyn Script>>,
37 ) -> Result<Box<dyn Script>> {
38 Ok(Box::new(SrcXmlScript::new(buf, encoding, config)?))
39 }
40
41 fn extensions(&self) -> &'static [&'static str] {
42 &["srcxml"]
43 }
44
45 fn script_type(&self) -> &'static ScriptType {
46 &ScriptType::EntisGls
47 }
48}
49
50#[derive(Debug)]
51/// Entis GLS engine XML Script
52pub struct SrcXmlScript {
53 handle: Handle,
54 lang: Option<String>,
55}
56
57impl SrcXmlScript {
58 /// Creates a new `SrcXmlScript` from the provided buffer and encoding.
59 ///
60 /// * `buf` - The buffer containing the XML data.
61 /// * `encoding` - The encoding of the XML data.
62 /// * `config` - Additional configuration options.
63 pub fn new(buf: Vec<u8>, encoding: Encoding, config: &ExtraConfig) -> Result<Self> {
64 let decoded = decode_to_string(encoding, &buf, false)?;
65 let dom = parse_document(RcDom::default(), Default::default())
66 .from_utf8()
67 .one(decoded.as_bytes());
68 {
69 let error = dom.errors.try_borrow()?;
70 for e in error.iter() {
71 eprintln!("WARN: Error parsing srcxml: {}", e);
72 crate::COUNTER.inc_warning();
73 }
74 }
75 Ok(Self {
76 handle: dom.document,
77 lang: config.entis_gls_srcxml_lang.clone(),
78 })
79 }
80}
81
82impl Script for SrcXmlScript {
83 fn default_output_script_type(&self) -> OutputScriptType {
84 OutputScriptType::Json
85 }
86
87 fn default_format_type(&self) -> FormatOptions {
88 FormatOptions::None
89 }
90
91 fn extract_messages(&self) -> Result<Vec<Message>> {
92 let mut messages = Vec::new();
93 let mut lang = self.lang.clone();
94 for i in self.handle.children.try_borrow()?.iter() {
95 if i.is_element("xscript") {
96 for code in i.children.try_borrow()?.iter() {
97 if code.is_element("code") {
98 for ins in code.children.try_borrow()?.iter() {
99 if ins.is_element("msg") {
100 let lan = match lang.as_ref() {
101 Some(l) => l.as_str(),
102 None => {
103 for attr in ins.element_attr_keys()? {
104 if attr.starts_with("name_")
105 || attr.starts_with("text_")
106 {
107 lang = Some(attr[5..].to_string());
108 break;
109 }
110 }
111 lang.as_ref().map(|s| s.as_str()).unwrap_or("")
112 }
113 };
114 let name_ref = if lan.is_empty() {
115 "name"
116 } else {
117 &format!("name_{}", lan)
118 };
119 let mut name = ins.get_attr_value(name_ref)?;
120 if name.as_ref().is_some_and(|s| s.is_empty()) {
121 name = None;
122 }
123 let text_ref = if lan.is_empty() {
124 "text"
125 } else {
126 &format!("text_{}", lan)
127 };
128 let message = ins
129 .get_attr_value(text_ref)?
130 .ok_or(anyhow::anyhow!("text not found"))?;
131 messages.push(Message { name, message })
132 } else if ins.is_element("select") {
133 for menu in ins.children.try_borrow()?.iter() {
134 if menu.is_element("menu") {
135 let lan = match lang.as_ref() {
136 Some(l) => l.as_str(),
137 None => {
138 for attr in ins.element_attr_keys()? {
139 if attr.starts_with("name_")
140 || attr.starts_with("text_")
141 {
142 lang = Some(attr[5..].to_string());
143 break;
144 }
145 }
146 lang.as_ref().map(|s| s.as_str()).unwrap_or("")
147 }
148 };
149 let text_ref = if lan.is_empty() {
150 "text"
151 } else {
152 &format!("text_{}", lan)
153 };
154 let message = menu
155 .get_attr_value(text_ref)?
156 .ok_or(anyhow::anyhow!("text not found"))?;
157 messages.push(Message {
158 name: None,
159 message,
160 });
161 }
162 }
163 }
164 }
165 }
166 }
167 }
168 }
169 Ok(messages)
170 }
171
172 fn import_messages<'a>(
173 &'a self,
174 messages: Vec<Message>,
175 mut file: Box<dyn WriteSeek + 'a>,
176 _filename: &str,
177 encoding: Encoding,
178 replacement: Option<&'a ReplacementTable>,
179 ) -> Result<()> {
180 let root = self.handle.deep_clone(None)?;
181 if !encoding.is_utf8() {
182 let len = root.children.try_borrow()?.len();
183 if len > 0 && root.children.try_borrow()?[0].is_processing_instruction("xml") {
184 root.change_child(0, |data| {
185 data.set_processing_instruction_content("version=\"1.0\"")
186 })?;
187 }
188 }
189 let mut lang = self.lang.clone();
190 let mut mess = messages.iter();
191 let mut mes = mess.next();
192 for i in root.children.try_borrow()?.iter() {
193 if i.is_element("xscript") {
194 for code in i.children.try_borrow()?.iter() {
195 if code.is_element("code") {
196 for ins in code.children.try_borrow()?.iter() {
197 if ins.is_element("msg") {
198 let m = match mes {
199 Some(m) => m,
200 None => {
201 return Err(anyhow::anyhow!(
202 "Not enough messages provided"
203 ));
204 }
205 };
206 let lan = match lang.as_ref() {
207 Some(l) => l.as_str(),
208 None => {
209 for attr in ins.element_attr_keys()? {
210 if attr.starts_with("name_")
211 || attr.starts_with("text_")
212 {
213 lang = Some(attr[5..].to_string());
214 break;
215 }
216 }
217 if lang.is_none() {
218 lang = Some(String::new());
219 }
220 lang.as_ref().map(|s| s.as_str()).unwrap_or("")
221 }
222 };
223 let name_ref = if lan.is_empty() {
224 "name"
225 } else {
226 &format!("name_{}", lan)
227 };
228 let name = match &m.name {
229 Some(name) => {
230 let mut name = name.clone();
231 if let Some(repl) = replacement {
232 for (k, v) in &repl.map {
233 name = name.replace(k, v);
234 }
235 }
236 name
237 }
238 None => String::new(),
239 };
240 ins.set_attr_value(name_ref, &name)?;
241 let message = m.message.clone();
242 let text_ref = if lan.is_empty() {
243 "text"
244 } else {
245 &format!("text_{}", lan)
246 };
247 ins.set_attr_value(text_ref, &message)?;
248 mes = mess.next();
249 } else if ins.is_element("select") {
250 for menu in ins.children.try_borrow()?.iter() {
251 if menu.is_element("menu") {
252 let m = match mes {
253 Some(m) => m,
254 None => {
255 return Err(anyhow::anyhow!(
256 "Not enough messages provided"
257 ));
258 }
259 };
260 let lan = match lang.as_ref() {
261 Some(l) => l.as_str(),
262 None => {
263 for attr in ins.element_attr_keys()? {
264 if attr.starts_with("name_")
265 || attr.starts_with("text_")
266 {
267 lang = Some(attr[5..].to_string());
268 break;
269 }
270 }
271 if lang.is_none() {
272 lang = Some(String::new());
273 }
274 lang.as_ref().map(|s| s.as_str()).unwrap_or("")
275 }
276 };
277 let text_ref = if lan.is_empty() {
278 "text"
279 } else {
280 &format!("text_{}", lan)
281 };
282 let mut message = m.message.clone();
283 if let Some(repl) = replacement {
284 for (k, v) in &repl.map {
285 message = message.replace(k, v);
286 }
287 }
288 menu.set_attr_value(text_ref, &message)?;
289 mes = mess.next();
290 }
291 }
292 }
293 }
294 }
295 }
296 }
297 }
298 let doc: SerializableHandle = root.clone().into();
299 let mut output = MemWriter::new();
300 serialize(&mut output, &doc, Default::default())
301 .map_err(|e| anyhow::anyhow!("Error serializing srcxml: {}", e))?;
302 if encoding.is_utf8() {
303 file.write_all(&output.data)?;
304 return Ok(());
305 }
306 let s = decode_to_string(Encoding::Utf8, &output.data, true)?;
307 let encoded = encode_string(encoding, &s, false)?;
308 file.write_all(&encoded)?;
309 Ok(())
310 }
311}